package com.youlai.quarkus.system.security;

import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.quarkus.security.UnauthorizedException;
import io.quarkus.security.credential.TokenCredential;
import io.quarkus.security.identity.IdentityProviderManager;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.request.AuthenticationRequest;
import io.quarkus.security.identity.request.TokenAuthenticationRequest;
import io.quarkus.vertx.http.runtime.QuarkusErrorHandler;
import io.quarkus.vertx.http.runtime.security.ChallengeData;
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Alternative;
import lombok.RequiredArgsConstructor;

import java.util.*;

/**
 *  JWT token 管理器
 *
 * @author haoxr
 * @since 2023/9/13
 */
/**
 *  TokenAuthenticationMechanism 是一个替代bean。
 * 启用备选方案。优先级可以是任何数字，以覆盖默认bean，但如果有多个替代方案，则优先级最高的优先级获胜。
 */
@RequiredArgsConstructor
@Alternative
@Priority(1)
@ApplicationScoped
public class TokenAuthenticationMechanism implements HttpAuthenticationMechanism {

    @Override
    public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
        return new HashSet<>(Arrays.asList(TokenAuthenticationRequest.class));
    }

    @Override
    public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
        List<String> authHeaders = context.request().headers().getAll(HttpHeaderNames.AUTHORIZATION);
        if (authHeaders != null) {
            for (String current : authHeaders) {
                String[] split = current.split(" ");
                String type = split[0];
                String token = split[1];
                TokenAuthenticationRequest credential = new TokenAuthenticationRequest(new TokenCredential(token, type));
                HttpSecurityUtils.setRoutingContextAttribute(credential, context);
                context.put(HttpAuthenticationMechanism.class.getName(), this);
                return identityProviderManager.authenticate(credential);
            }
        }
        // No suitable header has been found in this request,
        return Uni.createFrom().optional(Optional.empty());
    }

    /**
     * 无认证处理
     *
     * 1.使用 HttpAuthenticationMechanism
     *  通常不会直接在 HttpAuthenticationMechanism 中设置 HTTP 响应。相反，你可以抛出一个自定义的异常，
     *  这个异常随后会被一个 ExceptionMapper 捕获，然后设置 HTTP 401 响应
     * 2.使用 ExceptionMapper
     *  注意：步骤1这里一定要用自定义异常，不要 使用UnauthorizedException、AuthenticationException、ForbiddenException。这些异常
     *  优先会被 {@link QuarkusErrorHandler} 拦截器捕获
     */
    @Override
    public Uni<ChallengeData> getChallenge(RoutingContext context) {
        ChallengeData result = new ChallengeData(
                HttpResponseStatus.UNAUTHORIZED.code(),
                HttpHeaderNames.WWW_AUTHENTICATE,
                "Unauthorized"
                );
        return Uni.createFrom().item(result);
    }
}
